package org.noote.libs.bluetooth;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.UUID;
import java.util.concurrent.LinkedBlockingQueue;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.util.Log;

public class SPPClient {

	private static final UUID _SPP_UUID = UUID
			.fromString("00001101-0000-1000-8000-00805F9B34FB"); // SPP
	private static final String TAG = "SPPClient";

	private SPPEvent_Client _events = null;
	private int _bufferSize = 1024*32;
	
	private BluetoothAdapter mBluetoothAdapter = null;
	private BluetoothDevice mBluetoothDevice = null;
	private ConnectedThread mConnectedThread = null;
	
	private boolean _bIsConnected = false;
	private boolean _bIsDebug = false;
	
	public SPPClient(SPPEvent_Client events)
	{
		this._events = events;
	}
	
	public boolean isConnected(){return _bIsConnected;}

	public boolean connect(String sMACAdress) {
		mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
		if (mBluetoothAdapter == null) {
			_events.onError(
					"connect() : Bluetooth is not available.");
			Log.e(TAG, "Bluetooth is not available.");
			return false;
		}

		if (!mBluetoothAdapter.isEnabled()) {
			_events.onError(
					"connect() : Please enable your BT and re-run this program.");
			Log.e(TAG, "Please enable your BT and re-run this program.");
			return false;
		}

		BluetoothSocket mBluetoothSocket = null;
		try
		{
			mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(sMACAdress);
		} catch (IllegalArgumentException e) {
		}

		try {
			mBluetoothSocket = mBluetoothDevice
					.createRfcommSocketToServiceRecord(_SPP_UUID);
		} catch (IOException e) {
			_events.onError(
				"connect() : Socket creation failed.");	
			Log.e(TAG, "Socket creation failed.", e);
			return false;
		}

		mBluetoothAdapter.cancelDiscovery();

		try {
			mBluetoothSocket.connect();
			Log.v(TAG, "BT connection established, data transfer link open.");
		} catch (IOException e) {
			try {
				mBluetoothSocket.close();
			} catch (IOException e2) {
				Log.e(TAG, "Unable to close socket during connection failure",
						e2);
				_events.onError(
					"connect() : Unable to close socket during connection failure");				
			}
			return false;
		}

		(mConnectedThread = new ConnectedThread(mBluetoothSocket)).start();

		return true;
	}
	
	public boolean disconnect()
	{
		// TODO ! fix Crash :(
		if(mConnectedThread != null)
		{
			mConnectedThread.close(); // crash here
			mConnectedThread.interrupt();
			mConnectedThread = null;
		}
		_bIsConnected = false;
		return true;
	}
	
	public boolean sendMessage(String sMessage) {
		if(sMessage.length()==0)
		{
			if(_bIsDebug)
				Log.e(TAG, "sendMessage(): empty message");
			return false;
		}
		if (mConnectedThread != null)
			return mConnectedThread.sendQueued(sMessage);

		Log.e(TAG, "sendMessage(): not connected");

		return false;
	}

	private class ConnectedThread extends Thread {
		private static final String TAG = "SPPClient.ConnectedThread";

		private InputStream mmInStream = null;
		private OutputStream mmOutStream = null;
		private BluetoothSocket mSocket = null;
		private PrintWriter out = null;
		
		private final LinkedBlockingQueue<String> mQueue = new LinkedBlockingQueue<String>();

		public ConnectedThread(BluetoothSocket socket) {
			mSocket = socket;
			InputStream tmpIn = null;
			OutputStream tmpOut = null;

			// Get the input and output streams, using temp objects because
			// member streams are final
			try {
				tmpIn = socket.getInputStream();
				tmpOut = socket.getOutputStream();
			} catch (IOException e) {
				_events.onError(
					"ConnectedThread() : cannot get I/O handlers, catch IOException "+e.getMessage());
				return;
			}
			if(tmpIn == null || tmpOut==null)
			{
				_events.onError(
						"ConnectedThread() : cannot get I/O handlers");
				return;
			}

			mmInStream = tmpIn;
			mmOutStream = tmpOut;
			
			out = new PrintWriter(new BufferedWriter(
					new OutputStreamWriter(mmOutStream), _bufferSize), true);
		}

		@Override
		public void run() {
			if(mmInStream == null || mmOutStream == null)
				return;
			
			_bIsConnected = true;

			_events.onConnected();
	    	
            BufferedReader in = new BufferedReader(new InputStreamReader(mmInStream), _bufferSize);
	    	String line = null;
            try {
				while ((line = in.readLine()) != null) {
					if(_bIsDebug)
						Log.d(TAG, "Receive line : " + line);
					// have we a command or a result ?
					if(line.contains("#") && line.contains("$"))
					{
						int offset = 0;
						while(true)
						{
							int idx_start = line.indexOf("#", offset);
							if(idx_start == -1) break;
							int idx_end = line.indexOf("$", idx_start);
							if(idx_end == -1 || idx_start>idx_end) break;
							offset = idx_end+1;
							
							_events.onCmd(line.substring(idx_start+1, idx_end));
						}
					}
					else // else it's only text...
					{
						_events.onText(line);						
					}
				}
			} catch (IOException e) {
				_events.onError(
						"readLine() : I/O Exception " + e.toString());			
			}
			close();
			_bIsConnected = false;
	    }

		/* Call this from the main Activity to send data to the remote device */
		public boolean sendQueued(String sMessage) {
		
			try {
				mQueue.put(sMessage);
				
				while(!mQueue.isEmpty())
				{
					String sMsg = mQueue.take();
					
					out.println(sMsg);
					
					if(_bIsDebug)
						Log.d(TAG, "Sended message : " + sMessage);
				}
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
			return true;
		}
		
		/* Call this from the main Activity to shutdown the connection */
		public void close() {
			try {
				mSocket.close();
			} catch (IOException e) {
			}
			_events.onDisconnected();
		}
	}
}
